/** Converter & Formatter

  Written in the D programming language 1.0

Authors:	Wei Li	(oldrev@gmail.com)
License:	BSD
Copyright:	Copyright (C) 2007 by Wei Li. 
Date:		May 17 2007
*/

/*
 TODO:
  * 添加数组、关联数组支持
*/

module dotmars.base.convert;

import dotmars.base.typetraits;
import dotmars.base.stdexcept;
import dotmars.base.stdtypes;

import dotmars.base.math;
import dotmars.text.utf;
import dotmars.base.string;

import dotmars.io.console;

//36进制数位
const char[36] Digitals = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const char[36] DigitalsLower = "0123456789abcdefghijklmnopqrstuvwxyz";


private static  uint charToNumber(char c)
{
	if(c >= '0' && c <= '9')
		return c - '0';
	else if(c >= 'A' && c <= 'Z')
		return c - 'A' + 10;
	else if(c >= 'a' && c <= 'z')
		return c - 'a' + 10;

	throw new FormatException("Invalid digital character");
}

////////////////////////////////////////////////////////////////////////////////

final static class BitConverter
{
	enum Endian
	{
		Little,
		Big
	}

	version(LittleEndian)
		public const bool IsLittleEndian = true;
	version(BigEndian)
		public const bool IsLittleEndian = false;

	public static T reverseByteOrder(T)(T value)
	{
		static assert(IsIntegerType!(T) || IsCharType!(T) && T.sizeof > 1);
		static if(is(T == ushort))
			return fastReverseByteOrder16(value); 

		T ret = value;
		ubyte* first = cast(ubyte*)(&ret); 
		ubyte* last = first + T.sizeof - 1; 
		while (first < last)
		{
			ubyte tmp = *first;
			*first = *last;
			*last = tmp;
			++first;
			--last;
		}
		return ret;
	}

	private static ushort fastReverseByteOrder16(ushort value)
	{
		ushort ret = value >> 8;
		ret += value << 8;
		return ret;
	}
	
	public static bool toBoolean(ubyte[] bytes)
	in {
		assert(bytes !is null);
	}
	body {
		if(bytes.length < 1) 
			throw new ArgumentException("toBoolean: Empty array");
		return bytes[0] == 0;
	}

	public static ubyte[] getBytes(T)(T value)
	{
		ubyte* ptr = cast(ubyte*)(&value);
		return ptr[0..value.sizeof];
	}

}

////////////////////////////////////////////////////////////////////////////////

static class Integer
{
	//format INTs
	static void getChars(T)(T value, Sink!(char) sink, uint radix = 10, uint precision = 0, bool lower = false)
	in {
		static assert(IsIntegerType!(T));
		assert(radix >= 2 && radix <= 36);
	}
	body {
		//最长就是2进制了
		char[T.sizeof * 8 + 1] a;
		size_t index = a.length - 1;
		T u = abs(value);
		do {
			a[index--] = 
				lower ? DigitalsLower[u % radix] : Digitals[u % radix];
		}while(u /= radix)

		if(value < 0)a[index--] = '-';

		size_t len = a[index + 1 .. $].length;

		if(precision > len)
		{
			for(size_t j = 0; j < precision - len; j++)
				sink('0');
		}

		foreach(char c; a[index + 1 .. $])
			sink(c);
	}

	static string toString(T)(T value, uint radix = 10, uint precision = 0, bool lower = false)
	{
		string result;
		Sink!(char) sink = 
			(char c) { result ~= c; };
		getChars!(T)(value, sink, radix, precision, lower);
		return result;
	}

	static T parse(T)(string str)
	{
		bool sign = false;
		T value = 0;
		uint radix = 10;
		size_t i = 0;

		for(; i < str.length; i++)
		{
			char c = str[i];
			//跳过空白
			if(c == ' ') continue;

			if(c == '-') 
			{
				sign = true;
				continue;
			}

			if(c == '+')
				continue;

			//开始取进制标志

			//默认的十进制数
			if(c != '0')break;

			//就是一个简单的 '0'
			if(i >= str.length - 1)
				break;
		
			c = str[++i];
			switch(c)
			{
				case 'x':
				case 'X':
					radix = 16;
					i++;
					break;
				
				case 'b':
				case 'B':
					radix = 2;
					i++;
					break;

				//8进制
				default:
					radix = 8;
					break;
			}

			break;
		}

		//这里应该是纯数字序列
		for(; i < str.length; i++)
		{
			char c = str[i];

			uint d = charToNumber(c);
			if(d < radix)
				value = value * radix + d;
			else //到了这里应该有非法字符
				throw new FormatException("toInteger: invalid character");
		}

		if(sign)value = -value;
		return value;
	}
}


///////////////////////////////////////////////////////////////////////////////

static class Float
{
	static void getChars(T)(T value, Sink!(char) sink, uint precision = 6, 
			bool scientific = false, bool lower = false)
	in {
		static assert(IsFloatType!(T));
		assert(sink !is null);
	} 
	body {
		T x = abs(value);
		
		const T radix = 10.0;

		int exp = cast(int)log10(x);

		int intDigits = exp + 1;
		if(exp < 0)
		{
			intDigits = -exp + 1;
			x *= pow(radix, cast(T)-exp);
		}	
		else
			x /= pow(radix, cast(T)exp);

		if(scientific)
			intDigits = 1;

		int digits = intDigits + precision;

		if(value < 0)sink('-');

		for(size_t i = 0; i < digits; i++)
		{
			if(i == intDigits)
				sink('.');
			int digit = cast(int)x;
			sink('0' + digit);
			x = (x - digit) * radix;
		}

		if(scientific)
		{
			if(lower)
				sink('e');
			else
				sink('E');
			if(exp > 9)
			{
				sink('0' + exp / 10);
				sink('0' + exp % 10);
			}
			else
				sink('0' + exp);
		}
	}

	static string toString(T)(T value, uint precision = 6, bool scientific = false, bool lower = false)
	{
		string result;
		Sink!(char) sink = (char c) { result ~= c; };
		getChars!(T)(value, sink, precision, scientific, lower);
		return result;
	}
}

//////////////////////////////////////////////////////////////////////////////

static final class Convert
{
	public static string toString(byte value, uint radix = 10) {
		return Integer.toString!(int)(value, radix);
	}

	public static string toString(short value, uint radix = 10) {
		return Integer.toString!(int)(value, radix);
	}

	public static string toString(int value, uint radix = 10) {
		return Integer.toString!(int)(value, radix);
	}

	public static string toString(long value, uint radix = 10) {
		return Integer.toString!(long)(value, radix);
	}

	public static string toString(ubyte value, uint radix = 10) {
		return Integer.toString!(int)(value, radix);
	}

	public static string toString(ushort value, uint radix = 10) {
		return Integer.toString!(int)(value, radix);
	}

	public static string toString(uint value, uint radix = 10) {
		return Integer.toString!(uint)(value, radix);
	}

	public static string toString(ulong value, uint radix = 10) {
		return Integer.toString!(ulong)(value, radix);
	}

	// floats
	public static string toString(float value) {
		return Float.toString!(float)(value);
	}

	public static string toString(double value) {
		return Float.toString!(float)(value);
	}

	public static string toString(real value) {
		return Float.toString!(real)(value);
	}

	//imaginary numbers
	public static string toString(ifloat value) {
		return Float.toString!(float)(cast(float)value) ~ 'i';
	}

	public static string toString(idouble value) {
		return Float.toString!(double)(cast(double)value) ~ 'i';
	}

	public static string toString(ireal value) {
		return Float.toString!(real)(cast(real)value) ~ 'i';
	}

	//complex numbers
	public static string toString(cfloat value){
		return Float.toString!(float)(cast(float)value.re) ~ 
			'+' ~ Float.toString!(float)(cast(float)value.im) ~ 'i';
	}

	public static string toString(cdouble value){
		return Float.toString!(double)(cast(double)value.re) ~ 
			'+' ~ Float.toString!(double)(cast(double)value.im) ~ 'i';
	}

	public static string toString(creal value){
		return Float.toString!(real)(cast(real)value.re) ~ 
			'+' ~ Float.toString!(real)(cast(real)value.im) ~ 'i';
	}

	public static T fromString(T)(string str)
	{
		static if(IsIntegerType!(T))
		{
			return Integer.parse!(T)(str);
		}
		else
		{
			static assert(false, "not implemented");
		}
	}


	private static bool isDecimalChar(char c)
	{
		if(c >= '0' && c <= '9')return true;
		return false;
	}
}

///////////////////////////////////////////////////////////////////////////////

static class Formatter
{ 
	const size_t MaxArguments = 16;

	static void format(Sink!(char) sink, string fmt, ...)
	{
		format(sink, fmt, _argptr, _arguments);
	}

	static void format(Sink!(char) sink, Argument argPtr, TypeInfo[] argtis, string fmt)
	in {
		assert(sink !is null);
		assert(fmt !is null);
	}
	body {
		assert(argtis.length <= MaxArguments, "Formatter.format(): Too many arguments");
		Argument[MaxArguments] args = void;
		//填充参数指针
		foreach (size_t i, ti; argtis)
		{
			args[i] = argPtr;

			//来自于 stdarg.d，避免不同CPU架构中堆栈结构差异
			argPtr += (ti.tsize + size_t.sizeof - 1) & ~ (size_t.sizeof - 1);
		}

		doFormat(sink, fmt, args, argtis);
	}

	static string format(string fmt, ...) {
		string ret;
		Sink!(char) sink = (char c){ ret ~= c; } ;
		format(sink, fmt, _argptr, _arguments);
		return ret;
	}

	static size_t sprint(string dest, Argument argPtr, TypeInfo[] argtis, string fmt)
	in {
		assert(dest !is null);
	}
	body {
		size_t n = 0;
		Sink!(char) sink = 
			(char c) { dest[n++] = c; } ;
		format(sink, fmt, argPtr, argtis);
		return n;
	}

	static size_t sprint(string dest, string fmt, ...) {
		return sprint(dest, fmt, _argptr, _arguments);
	}

	private static void formatString(T)(Sink!(char) sink, string fmt,  T str)
	{
		static assert(is(T == string) || is(T == wstring) || is( T == dstring));
		if(fmt.length > 0)
			throw new FormatException("Not Supported");

		static if(is(T == string)) {
			foreach(char c; str)
				sink(c);
		}
		else static if(is(T == wstring)) {
			foreach(wchar c; str)
				.toUtf8(c, sink);
		}
		else static if(is(T == dstring)) {
			foreach(dchar c; str)
				.toUtf8(c, sink);
		}
	}

	private static void formatInteger(T)( Sink!(char) sink, string fmtstr, T i)
	in {
		static assert(IsIntegerType!(T));
	}
	body {

		bool lower = false;
		uint radix = 10;
		int precision = 0;

		if(fmtstr !is null && fmtstr.length >= 1)
		{
			char fmt = fmtstr[0];

			switch(fmt)
			{
				case 'x':
					radix = 16;
					lower = true;
					break;

				case 'X':
					radix = 16;
					break;

				case 'g':
				case 'G':
				case 'D':
				case 'd':
					radix = 10;
					break;

				default:
					throw new FormatException("Syntax error");
					break;
			}

			if(fmtstr.length > 1)
			{
				size_t l = 0;
				precision = extractDecimal(fmtstr[1..$], l);
				if(precision < 0)
					throw new FormatException("Invalid precision");
			}
		}
		
		Integer.getChars!(T)(i, sink, radix, precision, lower);
	}

	private static void formatFloat(T)(Sink!(char) sink, string fmtstr, T f)
	{
		uint precision = 6;
		bool scientific = false;
		bool lower = false;

		if(fmtstr !is null && fmtstr.length >= 1)
		{
			char fmt = fmtstr[0];

			switch(fmt)
			{
				case 'E':
					scientific = true;
					lower = false;
					break;

				case 'e':
					scientific = true;
					lower = true;
					break;

				case 'g':
				case 'G':
				case 'd':
				case 'D':
					scientific = false;
					break;

				default:
					throw new FormatException("Syntax error");
					break;
			}

			if(fmtstr.length > 1)
			{
				size_t l = 0;
				precision = extractDecimal(fmtstr[1..$], l);
				if(precision < 0)
					throw new FormatException("Invalid precision");
			}
		}

		
		Float.getChars!(T)(f, sink, precision, scientific, lower);  
	}

	private static void putSpaces(Sink!(char) sink, uint n) {
		for(uint i = 0; i < n; i++)
			sink(' ');
	}

	private static uint extractDecimal(string str, out size_t len) {
		assert(str !is null);
		len = 0;
		foreach(char c; str)
		{
			if(c >= '0' && c <= '9') len++;
			else break;
		}
		if(len == 0)
			throw new FormatException("Format: syntax error");
		return Convert.fromString!(uint)(str[0 .. len]);
	}


	private static size_t findRightBrach(string str) 
	{
		assert(str !is null);

		foreach(size_t i, char c; str) {
			if(c == '}')
				return i;
		}

		// "{}" have not matched.
		throw new FormatException("Format: Syntax error");
	}

	private static void doFormat(Sink!(char) sink, string fmt, Argument[] args, TypeInfo[] ti)
	{
		for(size_t i = 0; i < fmt.length;)
		{
			// skip the escape char
			for(; i < fmt.length && fmt[i] != '{'; i++)
				sink(fmt[i]);

			if(i >= fmt.length)break;

			// "{{"
			if(i < fmt.length - 1 && fmt[i + 1] == '{')
			{
				i += 2;
				sink('{');
				continue;
			}

			size_t beginBrach = i;
			size_t endBrach = i + findRightBrach(fmt[i .. $]);

			// parsing the format string
			doParse(sink, fmt[beginBrach + 1 .. endBrach], args, ti);

			i = endBrach + 1; 
		}
	}


	//解析格式化字符串并执行
	private static void doParse(Sink!(char) sink, string fmt, Argument[] args, TypeInfo[] ti)
	in {
		assert(fmt !is null);
		assert(args !is null);
		assert(ti !is null);
	}
	body {
		//{INDEX,+/-ALIGNMENT:FORMAT}
		size_t i = 0, len = 0;
		// skip spaces
		while(i < fmt.length && fmt[i] == ' ') i++;
		// extract the index
		uint index = extractDecimal(fmt[i..$], len);
		if(index >= ti.length)
			throw new FormatException("Invalid index of place holder");

		i += len;

		//skip spaces
		while(i < fmt.length && fmt[i] == ' ') i++;

		//deal with alignment
		int alignment = 0;
		if(i < fmt.length && fmt[i] == ',')
		{
			i++;
			while(i < fmt.length && fmt[i] == ' ') i++;
			bool sign = false;
			if(fmt[i] == '-')
			{
				i++;
				sign = true;
			}
			if(fmt[i] == '+')
				i++;

			alignment = extractDecimal(fmt[i..$], len);
			if(sign) alignment = -alignment;
			i += len;
		}

		//skip spaces
		while(i < fmt.length && fmt[i] == ' ') i++;

		string fmtstr = null;
		if(i < fmt.length && fmt[i] == ':')
		{
			i++;
			while(i < fmt.length && fmt[i] == ' ') i++;
			size_t l = i;
			while(l < fmt.length && fmt[l] != ' ') l++;
			fmtstr = fmt[i .. l];
		}
		
		handleAlignment(sink, alignment, fmtstr, args[index], ti[index]);
	}

	private static void formatDisp(Sink!(char) sink, string fmtstr, Argument arg, TypeInfo ti) 
	in {
		assert(sink !is null);
		assert(ti !is null);
	}
	body {
		// integers
		if(ti == typeid(byte)) {
			byte* pval = cast(byte*)arg;
			formatInteger!(byte)(sink, fmtstr, *pval);
		}
		else if(ti == typeid(ubyte)) {
			ubyte* pval = cast(ubyte*)arg;
			formatInteger!(ubyte)(sink, fmtstr, *pval);
		}
		else if (ti == typeid(short)) {
			short* pval = cast(short*)arg;
			formatInteger!(short)(sink, fmtstr, *pval);
		}
		else if(ti == typeid(ushort)) {
			ushort* pval = cast(ushort*)arg;
			formatInteger!(ushort)(sink, fmtstr, *pval);
		}
		else if(ti == typeid(int)) {
			int* pval = cast(int*)arg;
			formatInteger!(int)(sink, fmtstr, *pval);
		} 
		else if(ti == typeid(uint)) {
			uint* pval = cast(uint*)arg;
			formatInteger!(uint)(sink, fmtstr, *pval);
		}
		else if(ti == typeid(long)) {
			long* pval = cast(long*)arg;
			formatInteger!(long)(sink, fmtstr, *pval);
		}
		else if(ti == typeid(ulong)) {
			ulong* pval = cast(ulong*)arg;
			formatInteger!(ulong)(sink, fmtstr, *pval);
		}
		// floats
		else if(ti == typeid(float)) {
			float* pval = cast(float*)arg;
			formatFloat!(float)(sink, fmtstr, *pval);
		}
		else if(ti == typeid(double)) {
			double* pval = cast(double*)arg;
			formatFloat!(double)(sink, fmtstr,*pval);
		}
		else if(ti == typeid(real)) {
			real* pval = cast(real*)arg;
			formatFloat!(real)(sink, fmtstr, *pval);
		}
		else if(ti == typeid(ifloat))
		{
			float* pval = cast(float*)arg;
			formatFloat!(float)(sink, fmtstr, *pval);
		}
		else if(ti == typeid(idouble))
		{
			double* pval = cast(double*)arg;
			formatFloat!(double)(sink, fmtstr, *pval);
		}
		else if(ti == typeid(ireal))
		{
			real* pval = cast(real*)arg;
			formatFloat!(real)(sink, fmtstr, *pval);
		}
		/*
		else if(ti == typeid(cfloat))
		{
			cfloat* pval = cast(cfloat*)arg;
			formatOne(fmtstr, sink, *pval);
		}
		else if(ti == typeid(cdouble))
		{
			cdouble* pval = cast(cdouble*)arg;
			formatOne(fmtstr, sink, *pval);
		}
		else if(ti == typeid(creal))
		{
			creal* pval = cast(creal*)arg;
			formatOne(fmtstr, sink, *pval);
		}*/
		//string type
		else if(ti == typeid(string)) {
			string *pstr = cast(string*)arg;
			formatString!(string)(sink, fmtstr, *pstr);
		}
		else if(ti == typeid(wstring)) {
			wstring *pstr = cast(wstring*)arg;
			formatString!(wstring)(sink, fmtstr, *pstr);
		}
		else if(ti == typeid(dstring)) {
			dstring *pstr = cast(dstring*)arg;
			formatString!(dstring)(sink, fmtstr, *pstr);
		}
		//character type
		else if(ti == typeid(char)) {
			char *pstr = cast(char*)arg;
			formatString!(string)(sink, fmtstr, pstr[0..1]);
		}
		else if(ti == typeid(wchar)) {
			wchar *pstr = cast(wchar*)arg;
			formatString!(wstring)(sink, fmtstr, pstr[0..1]);
		}
		else if(ti == typeid(dchar)) {
			dchar *pstr = cast(dchar*)arg;
			formatString!(dstring)(sink, fmtstr, pstr[0..1]);
		}
		//boolean
		else if(ti == typeid(bool)) {
			bool *pval = cast(bool*)arg;
			formatString!(string)(sink, fmtstr, *pval ? "true" : "false");
		}
		//class
		else if(ti.classinfo.name == "TypeInfo_Class") 
		{
			Object* pobj = cast(Object*) arg;

			version(Tango) {
				string str = (*pobj) is null ? "<null>" : (*pobj).toUtf8;
			}
			else {
				string str = (*pobj) is null ? "<null>" : (*pobj).toString;
			}

			formatString!(string)(sink, fmtstr, str);
		}
		else {
			throw new FormatException("Unknown type");
		}

	}


	private static void handleAlignment(Sink!(char) sink, int alignment, 
			string fmt, Argument arg, TypeInfo ti)
	{
		int count = 0;
		Sink!(char) drySink = (char c) { count++; };
		formatDisp(drySink, fmt, arg, ti);
		int padding = abs(alignment) - count;

		if(padding > 0 && alignment > 0)
			putSpaces(sink, padding);

		formatDisp(sink, fmt, arg, ti);

		//right align
		if(padding > 0 && alignment < 0)
			putSpaces(sink, padding);
	}

}

